"""RDFshow template set support."""

import rdflib

import template
import parser
from util import AncestryTracker

__all__ = ['TemplateSet']


class TemplateSet(object):
    """A set of templates to be used together.
    
    Can contain any number of templates, which are then rendered together. For
    example, if a node template requests a statement to be rendered, an
    appropriate statement template is selected from the same template set.
    """
    def __init__(self):
        """Create a new empty template set."""
        self._tpls = {          # Templates arranged by their type
            template.Template.NODE_SET: [],
            template.Template.NODE: [],
            template.Template.STMT_SET: [],
            template.Template.STMT: []
        }
    
    def _select_tpl(self, type_, flavour, arg=None, tracker=None):
        """Select a template of `type_` and `flavour` for rendering `arg`.
        
        If an acceptable template is found, return it; otherwise, return None.
        `flavour` may be None. `arg` may or may not be None depending on
        `type_`. `tracker` is the ancestry tracker to use.
        """
        if type_ in (template.Template.NODE_SET, template.Template.STMT_SET):
            # Select the first template with a matching flavour.
            for candidate in self._tpls[type_]:
                if candidate.flavour == flavour:
                    return candidate
        else:
            # Target classes/properties must be taken into consideration.
            is_property = (type_ == template.Template.STMT)
            ancestry = tracker.get_ancestry(arg, is_property)
            best_match = None
            best_hops = 10000   # approaches infinity
            for candidate in self._tpls[type_]:
                if candidate.flavour == flavour:
                    # Determine this candidate's proximity to the argument.
                    if candidate.for_nodes is None:
                        hops = 9999
                    else:
                        relevant_targets = [ancestry[node]
                                            for node in candidate.for_nodes
                                            if node in ancestry]
                        if len(relevant_targets) < 1:
                            continue
                        else:
                            hops = min(relevant_targets)
                    if hops < best_hops:
                        # New best match.
                        best_match = candidate
                        best_hops = hops
            return best_match
    
    def add_from_code(self, code):
        """Parse unicode `code` and add resulting templates to this set.
        
        Raise `LexError` if the code is lexically invalid, `ParseError` if it
        is syntactially or semantically invalid. Return the number of templates
        added.
        """
        tpls = parser.TemplateParser(code).parse()
        for tpl in tpls:
            self._tpls[tpl.type].append(tpl)
        return len(tpls)
    
    def render_node_set(self, nodes, graph, output, flavour=None):
        """Render node set `nodes` into file `output` using `graph`.
        
        `nodes` should be a list of RDFLib URIRefs, BNodes or Literals. Only
        select templates with `flavour`. If no suitable templates exist in the
        template set, output nothing.
        """
        tpl = self._select_tpl(template.Template.NODE_SET, flavour)
        if tpl is not None:
            ctx = template.Context(node_set=nodes, node=None, stmt_set=None,
                                   stmt=None, tpl_set=self)
            tpl.render(ctx, graph, output)
    
    def render_stmt_set(self, stmts, graph, output, flavour=None):
        """Render statement set `stmts` into file `output` using `graph`.
        
        `stmts` should be an RDFLib graph. Only select templates with
        `flavour`. If no suitable templates exist in the template set, output
        nothing.
        """
        tpl = self._select_tpl(template.Template.STMT_SET, flavour)
        if tpl is not None:
            ctx = template.Context(node_set=None, node=None, stmt_set=stmts,
                                   stmt=None, tpl_set=self)
            tpl.render(ctx, graph, output)
    
    def render_node(self, node, graph, output, flavour=None):
        """Render `node` into file `output` using `graph`.
        
        `node` should be an RDFLib URIRef, BNode or Literal. Only select
        templates with `flavour`. If no suitable templates exist in the
        template set, output nothing.
        """
        tpl = self._select_tpl(template.Template.NODE, flavour, arg=node,
                               tracker=AncestryTracker(graph))
        if tpl is not None:
            ctx = template.Context(node_set=None, node=node, stmt_set=None,
                                   stmt=None, tpl_set=self)
            tpl.render(ctx, graph, output)
    
    def render_stmt(self, stmt, graph, output, flavour=None):
        """Render `stmt` into file `output` using `graph`.
        
        `stmt` should be a (subject, predicate, object) triple, as in RDFLib.
        Only select templates with `flavour`. If no suitable templates exist
        in the template set, output nothing.
        """
        s, p, o = stmt
        tpl = self._select_tpl(template.Template.STMT, flavour, arg=p,
                               tracker=AncestryTracker(graph))
        if tpl is not None:
            ctx = template.Context(node_set=None, node=None, stmt_set=None,
                                   stmt=stmt, tpl_set=self)
            tpl.render(ctx, graph, output)
